#include "iproute/epoll.h"
#include "include/unet_def.h"

namespace unet {

Epoll::Epoll()
    : ep_fd_(INVALIED_FD),
      stop_(true) {
}

Epoll::~Epoll() {
  Stop();

  if (ep_fd_ != INVALIED_FD) {
    ::close(ep_fd_);
  }
}

bool Epoll::Init() {
  if (ep_fd_ != INVALIED_FD)
    return true;

  ep_fd_ = ::epoll_create1(EPOLL_CLOEXEC);
  if (ep_fd_ == INVALIED_FD) {
    LOG(WARNING) << "epoll_create error";
    return false;
  }

  epoll_event ev = { -1 };
  events_.resize(20, ev);

  return true;
}

bool Epoll::Add(Event* ev) {
  if (!CheckEvent(ev))
    return false;
  if (ev_map_.count(ev->fd) != 0)
    return false;
  epoll_event event = { -1, 0 };
  event.data.ptr = (void*) ev;

  uint8 flag = ConvertToEvent(ev->event);
  int32 ret = ::epoll_ctl(ep_fd_, EPOLL_CTL_ADD, ev->fd, &event);
  if (ret == -1) {
    PLOG(WARNING) << "epoll_ctl error";
    return false;
  }

  return ev_map_.insert(std::make_pair(ev->fd, ev)).second;
}

bool Epoll::Mod(Event* ev) {
  if (!CheckEvent(ev))
    return false;
  if (ev_map_.count(ev->fd) != 1)
    return false;
  epoll_event event = { -1, 0 };
  event.data.ptr = (void*) ev;

  uint8 flag = ConvertToEvent(ev->event);
  int32 ret = ::epoll_ctl(ep_fd_, EPOLL_CTL_MOD, ev->fd, &event);
  if (ret == -1) {
    PLOG(WARNING) << "epoll_ctl error";
    return false;
  }

  return true;
}

void Epoll::Del(Event* ev) {
  if (!CheckEvent(ev))
    return;
  if (ev_map_.count(ev->fd) != 1)
    return;

  epoll_event event = { -1, 0 };
  event.data.ptr = (void*) ev;

  uint8 flag = ConvertToEvent(ev->event);
  int32 ret = ::epoll_ctl(ep_fd_, EPOLL_CTL_DEL, ev->fd, &event);
  if (ret == -1) {
    PLOG(WARNING) << "epoll_ctl error";
    return;
  }

  ev_map_.erase(ev->fd);
}

void Epoll::Loop() {
  if (ep_fd_ == INVALIED_FD)
    return;
  stop_ = false;

  while (!stop_) {
    int32 ret = ::epoll_wait(ep_fd_, events_.data(), events_.size(), 1);
    if (ret == -1) {
      if (errno != EINTR) {
        LOG(WARNING) << "epoll_wait error";
      }
      continue;
    }

    for (uint32 i = 0; i < ret; ++i) {
      epoll_event ep_ev = events_[i];
      const Event* const event = (const Event*) ep_ev.data.ptr;
      CHECK_NOTNULL(event);

      uint8 flag = ConvertFromEvent(ep_ev.events);
      event->cb(event->fd, flag, event->contex);
    }
  }
}

void Epoll::LoopInAnotherThread() {
  // todo
}

void Epoll::Stop() {
  stop_ = true;
}

bool Epoll::CheckEvent(Event* ev) {
  if (ev == NULL)
    return false;
  if (ev->contex == NULL)
    return false;
  if (ev->fd == INVALIED_FD)
    return false;

  return true;
}

uint8 ConvertToEvent(uint8 event) {
  uint8 flag = 0;
  if (event & EV_READ) {
    flag |= EPOLLIN;
  }
  if (event & EV_WRITE) {
    flag |= EPOLLOUT;
  }

  return flag;
}

uint8 ConvertFromEvent(uint8 event) {
  uint8 flag = 0;
  if (event & EPOLLIN) {
    flag |= EV_READ;
  }
  if (event & EPOLLOUT) {
    flag |= EV_WRITE;
  }

  return flag;
}

}
